/*******************************************************************************
 * Copyright (c) 2018, 2020 THALES GLOBAL SERVICES.
 * 
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 * 
 * SPDX-License-Identifier: EPL-2.0
 * 
 * Contributors:
 *    Thales - initial API and implementation
 *******************************************************************************/
package org.polarsys.capella.test.validation.rules.ju.handlers;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.lang.WordUtils;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.internal.resources.Project;
import org.eclipse.core.internal.resources.VariableDescription;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.validation.service.ConstraintRegistry;
import org.eclipse.emf.validation.service.IConstraintDescriptor;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.handlers.HandlerUtil;
import org.polarsys.capella.common.ui.services.commands.AbstractUiHandler;

/**
 * Generate validation rule documentation for all rules of the platform starting with "rootCategoryId".
 * 
 * As a addon can contribute to Capella validation rules, we don't want them to be in the capella.validation.doc plugin,
 * but in the viewpoint documentation plugin. To make it work, the documentation plugin references which validation
 * plugins we want the documentation about.
 *
 * Steps:
 * 
 * - Import validation rule documentation plugin. - On an element, right click, generate documentation mediawiki -
 * Generate html pages with build.xml
 * 
 * How it works:
 * 
 * For all plugins contributing validation rules, it will look for a documentation plugin available in the workspace and
 * generate into it.
 * 
 * A documentation plugin references validation rules plugins within the .project :
 * 
 * <pre>
 *  <variableList>
 *   <variable>
 *    <name>projects</name>
 *    <value>plugin1;plugin2;etc</value>
 *   </variable>
 *  </variableList>
 * </pre>
 */
public class GenerateDocValidation extends AbstractUiHandler {

  private final String rootCategoryId = "capella.category"; //$NON-NLS-1$
  private final String targetHtmlFileName = "ValidationRules.mediawiki"; //$NON-NLS-1$

  /**
   * Retrieve all projects containing validation rules covered by the given documentation project
   */
  public Collection<String> getPluginsToProcess(IProject project) {
    Collection<String> plugins = new ArrayList<String>();
    HashMap<String, VariableDescription> variables = ((Project) project).internalGetDescription().getVariables();
    if (variables != null) {
      VariableDescription desc = variables.get("projects");
      if (desc != null) {
        plugins.addAll(Arrays.asList(desc.getValue().split(";")));
      }
    }
    return plugins;
  }

  /**
   * Retrieve all validation rules projects that will be generated by the documentation projects available in the
   * workspace
   */
  public List<String> getAllPluginsToProcess() {
    return Arrays.asList(ResourcesPlugin.getWorkspace().getRoot().getProjects()).stream()
        .flatMap(x -> getPluginsToProcess(x).stream()).distinct().collect(Collectors.toList());
  }

  /**
   * Returns for each validation rules project its covering documentation project
   */
  public Map<String, IProject> getProcessingDocumentationProjects() {
    Map<String, IProject> plugins = new HashMap<>();
    for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
      for (String plugin : getPluginsToProcess(project)) {
        plugins.put(plugin, project);
      }
    }
    return plugins;
  }

  /**
   * Create recursively the given folder
   */
  public IFolder create(IFolder folder) {
    IProject project = folder.getProject();
    IResource parent = project;
    for (String segment : Arrays.asList(folder.getProjectRelativePath().segments())) {
      parent = (parent instanceof IProject ? ((IProject) parent).getFolder(segment)
          : (((IFolder) parent).getFolder(segment)));
      if (!parent.exists()) {
        try {
          ((IFolder) parent).create(true, true, new NullProgressMonitor());
        } catch (CoreException e) {
          e.printStackTrace();
        }
      }
    }
    return folder;
  }

  @Override
  public Object execute(ExecutionEvent event) throws ExecutionException {
    ConstraintRegistry reg = ConstraintRegistry.getInstance();

    List<String> pluginsToProcess = getAllPluginsToProcess();

    List<IConstraintDescriptor> allRegisteredRules = reg.getAllDescriptors().stream()
        .filter(x -> x.getCategories().iterator().next().getPath().startsWith(rootCategoryId))
        .collect(Collectors.toList());

    List<String> pluginsWithRules = allRegisteredRules.stream().map(x -> x.getPluginId()).distinct()
        .collect(Collectors.toList());
    
    if (pluginsToProcess.isEmpty()) {
      MessageDialog.openInformation(HandlerUtil.getActiveShell(event), "Import documentation", NLS.bind(
          "There is no documentation plugin covering one of these plugins: {0}\n\nPlease import a validation documentation plugin into the workspace.\n\nTo make it generate towards a documentation plugin, please inspire from <.project> file of capella.validation.doc to make it target of one of validation rule plugin",
          pluginsWithRules.stream().collect(Collectors.joining("\n"))));
      return false;
    }
    
    Map<String, IProject> processingProjects = getProcessingDocumentationProjects();

    List<String> unmatchedPlugins = new ArrayList<>(pluginsWithRules);
    unmatchedPlugins.removeAll(pluginsToProcess);

    MessageDialog.openInformation(HandlerUtil.getActiveShell(event), "Generation",
        NLS.bind("Documentation will be generated for : \n\n{0}\n\nDocumentation will not be generated for:\n\n{1}",
            pluginsToProcess.stream().collect(Collectors.joining("\n")),
            unmatchedPlugins.stream().collect(Collectors.joining("\n"))));

    List<String> allRegisteredCategories = allRegisteredRules.stream()
        .map(x -> x.getCategories().iterator().next().getPath()).distinct().collect(Collectors.toList());
    Collections.sort(allRegisteredCategories);

    for (String category : allRegisteredCategories) {
      List<IConstraintDescriptor> coveredRulesByCategory = allRegisteredRules.stream()
          .filter(x -> x.getCategories().iterator().next().getPath().equals(category)).collect(Collectors.toList());
      coveredRulesByCategory.sort(new Comparator<IConstraintDescriptor>() {
        @Override
        public int compare(IConstraintDescriptor o1, IConstraintDescriptor o2) {
          String id1 = o1.getId().substring(o1.getId().lastIndexOf(".") + 1);
          String id2 = o2.getId().substring(o2.getId().lastIndexOf(".") + 1);
          return id1.compareTo(id2);
        }
      });

      for (IProject project : coveredRulesByCategory.stream().map(r -> processingProjects.get(r.getPluginId()))
          .distinct().collect(Collectors.toList())) {
        if (project == null) {
          continue;
        }
        List<IConstraintDescriptor> coveredRulesByProject = coveredRulesByCategory.stream()
            .filter(r -> processingProjects.get(r.getPluginId()) == project).collect(Collectors.toList());

        String folder = category.replace(rootCategoryId, "html/Validation Rules");

        IFolder outputFolder = create(project.getFolder(folder));
        IFile outputFile = project.getFile(folder + "/" + targetHtmlFileName);

        String result = "";
        result += "      \n";
        result += "= " + WordUtils.capitalizeFully(
            category.replace(rootCategoryId + "/", "").replaceAll("/", " > ").replaceAll("_", " ")) + " =\n";

        IFile images = project.getFile("html/Images");
        IPath path = images.getFullPath().makeRelativeTo(outputFolder.getFullPath());
        System.out.println(path);

        for (IConstraintDescriptor rule : coveredRulesByProject) {
          String severity = rule.getSeverity().getLiteral();
          result += "<br>\n";
          result += "{| class=\"VALIDATION-RULE\"\n";
          result += "!|[[Image:" + path + "/" + severity.toLowerCase() + ".gif|" + severity + "]]\n";
          result += "|" + rule.getName() + " \n";
          result += "|-\n";
          result += "| colspan=\"2\"|" + rule.getDescription() + "\n";
          result += "|}\n";
        }
        System.out.println(result);

        try {
          if (outputFile.exists()) {
            outputFile.setContents(new ByteArrayInputStream(result.getBytes()), 0, new NullProgressMonitor());
          } else {
            outputFile.create(new ByteArrayInputStream(result.getBytes()), true, new NullProgressMonitor());
          }
        } catch (CoreException e) {
          e.printStackTrace();
        }
      }

      for (IConstraintDescriptor rule : coveredRulesByCategory) {
        System.out.println(rule);
        System.out.println(processingProjects.get(rule.getPluginId()));
      }
    }

    MessageDialog.openInformation(HandlerUtil.getActiveShell(event), "Generation",
        "Documentation mediawiki has been generated. Don't forget to generate html from mediawiki");

    return null;
  }

}
